import { GLAPI, PROXY } from './GLAPI.service';
import { File, SQLite } from 'ionic-native';
import { Platform } from 'ionic-angular';
import { Http, Response, ResponseContentType } from '@angular/http';
import { Injectable } from '@angular/core';
import { GLBookContent } from './GLBookContent';
import { GLBookChapter } from './GLBookChapter';
import { GLFolderLike } from './GLFolderLike';
import { GLFolder } from './GLFolder';
import { Observable } from 'rxjs';
import 'rxjs/Rx';

import * as pako from 'pako';
import * as hat from 'hat';

let API_URI = PROXY ? "/api" : "http://tech.lds.org/glweb";

@Injectable()
export class GLAPI_glweb2_ZBook extends GLAPI {

  constructor(private http: Http, platform: Platform) {
    super(platform);
  }

  getCatalogInfo() {
    return this.getAPIResource("catalog.json", "catalog.query").then((content) => {
      return JSON.parse(content).catalog;
    });
  }

  getFile(filepath: string, json: boolean = false, url: string) {
    filepath = "api3/" + filepath;
    return this.checkFile(filepath).then((found: boolean): PromiseLike<string> => {
      console.log("Found? " + found)
      if (found) {
        console.log("Reading " + filepath);
        console.log(this.fs);
        return File.readAsText(this.fs, filepath)
          .then((content: string) => {
            if (json) {
              // If it's not valid, this should throw an error and redownload
              JSON.parse(content);
            }
            return content;
          }).catch((err) => {
            console.error(err);
            return this.download(url, filepath);
          })
      }
      else {
        return this.download(url, filepath);
      }
    });
  }

  getBinFile(filepath: string, url: string) {
    filepath = "api3/" + filepath;
    if (PROXY) {
      url = url.replace("http://broadcast3.lds.org", "/broadcast3");
    }
    return this.checkFile(filepath).then((found: boolean) => {
      console.log("Found: " + found);
      if (found) {
        console.log("Reading: " + filepath);
        return File.readAsArrayBuffer(this.fs, filepath)
          .catch((err) => {
            console.error(err);
            return this.downloadBin(url, filepath);
          });
      }
      else {
        return this.downloadBin(url, filepath);
      }
    });
  }

  getAPIResource(filepath: string, action: string, params?) {
    params = params || {};
    params.action = action;
    params.platformid = 1;
    params.languageid = 1;
    return this.getFile(filepath, true, API_URI + this.query(params));
  }

  private checkFile(filepath: string) {
    return this.ready().then(() => {
      return File.checkFile(this.fs, filepath).catch(this.codeOne);
    });
  }

  private codeOne(err) {
    // Sometimes it throws NOT_FOUND_ERRs?
    if (err.code === 1) {
      return false;
    }
    throw err;
  }

  private getBookPath(id: string) {
    return "books" + id + ".zbook";
  }

  isBookAvailable(id: string) {
    return this.checkFile(this.getBookPath(id));
  }

  getBook(id: string, url: string, parentFolder: GLFolderLike) {
    return this.getBinFile(this.getBookPath(id), url).then((content) => {
      console.log("got book");
      let result = pako.ungzip(content);
      let tmpfile = hat() + ".sqlite";
      let tmppath = "databases/" + tmpfile;
      console.log(this.fs2);
      return this.mkdirp(tmppath, this.fs2)
        .then(() => File.writeFile(this.fs2, tmppath, new Blob([result])))
        .then(() => this.ready(), (err) => {
          console.log("Ugh");
          console.error(err);
          throw err;
        })
        .then(() => {
          let db = new SQLite();
          return db.openDatabase({
            name: tmpfile,
            location: "default"
          }).then((): Promise<any[]> => {
            return Promise.all([db.executeSql("SELECT * FROM bookmeta", {}), db.executeSql("SELECT * FROM nodes", {})]);
          }).then((values) => {
            console.log(values[0].rows, values[1].rows);
            let items = {};
            for (var i = 0; i < values[1].rows.length; i++) {
              let node = values[1].rows.item(i);
              let subItem = {
                title: node.title,
                content: node.content,
                uri: node.uri,
                parent_id: node.parent_id,
                id: node.id,
                refs: node.refs
              };
              items[node.id] = subItem;
            }
            let list = this.unflatten(items);

            return new GLBookContent({
              title: values[0].rows.item(0).name,
              nodes: list
            }, parentFolder);
          });
        })
    })
  }

  private unflatten(items: any, parent_id?: String) {
    let list = [];
    for (let key in items) {
      let value = items[key];
      if (value.parent_id == parent_id) {
        console.log(value.parent_id, parent_id, value.id);
        value.nodes = this.unflatten(items, value.id);
        list.push(value);
      }
    }
    return list;
  }

  loadCatalog() {
    return this.getCatalogInfo().then((info) => new GLFolder(info, this));
  }

  private download(url, filepath) {
    let res: Observable<Response> = this.http.get(url);
    return res.map((value: Response) => {
      console.log("got value");
      return value.text();
    }).toPromise().then((content: string) => {
      console.log("got content");
      console.log(this.fs);
      console.log(filepath);
      return this.mkdirp(filepath).then(() => {
        console.log("we made it here");
        return File.createFile(this.fs, filepath, true);
      }).then(() => {
        console.log("created file");
        return File.writeFile(this.fs, filepath, content, {
          replace: true
        })
          .then(() => {
            console.log("wrote to file");
            return content;
          });
      }).catch((err) => {
        console.log("Something's wrong here");
        console.error(err);
      });
    });
  }

  private downloadBin(url, filepath) {
    let res: Observable<Response> = this.http.get(url, { responseType: ResponseContentType.Blob });
    return res.map((value: Response) => {
      console.log("got value");
      return value.blob();
    }).toPromise().then((blob: Blob) => {
      console.log("got content");
      console.log(this.fs);
      console.log(filepath);
      return this.mkdirp(filepath).then(() => {
        console.log("we made it here");
        return File.createFile(this.fs, filepath, true);
      }).then(() => {
        console.log("created file");
        return File.writeFile(this.fs, filepath, blob, {
          replace: true
        })
          .then(() => {
            console.log("wrote to file");
            return new Promise((resolve, reject) => {
              let reader = new FileReader();
              reader.onload = () => {
                resolve(reader.result);
              };
              reader.readAsArrayBuffer(blob);
            });
          });
      }).catch((err) => {
        console.log("Something's wrong here");
        console.error(err);
      });
    });
  }

  private mkdirp(filepath: string, fs: string = this.fs) {
    let ind = filepath.lastIndexOf("/");
    if (ind < 0) {
      return Promise.resolve();
    }
    let newPath = filepath.substr(0, ind);
    console.log(newPath);
    return File.checkDir(fs, newPath).catch(this.codeOne).then((found: boolean) => {
      if (found) {
        console.log("already here");
        return Promise.resolve();
      }
      return this.mkdirp(newPath, fs).then(() => File.createDir(fs, newPath, true)).then(() => {
        console.log("created " + newPath);
      }, (err) => {
        console.log("creating dir failed");
        console.error(err);
      });
    });
  }
}